/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Copyright 2002-2003 by Mark Wolgemuth, All rights reserved.             */
/* Copyright 2002-2003 by Employease, Inc.  All rights reserved.           */
/*                                                                         */
/* Redistribution and use in source and binary forms, with or without      */
/* modification, are permitted provided that the following conditions      */
/* are met:                                                                */
/*                                                                         */
/*    1. Redistributions of source code must retain the above copyright    */
/* notice, this list of conditions and the following disclaimer.           */
/*    2. Redistributions in binary form must reproduce the above copyright */
/* notice, this list of conditions and the following disclaimer in the     */
/* documentation and/or other materials provided with the distribution.    */
/*                                                                         */
/* THIS SOFTWARE IS PROVIDED BY THE MOD_ATHENA PROJECT ``AS IS'' AND ANY   */
/* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE       */
/* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR      */
/* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FREEBSD PROJECT OR        */
/* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,   */
/* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,     */
/* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR      */
/* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY     */
/* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT            */
/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE   */
/* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.    */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "athd.h"
#include "apr_getopt.h"

#define ATHD_ATHENAD_CMD_ARGS     "vf:D:k:d:Xh?"

static apr_pool_t *_current_pglobal = NULL;
static ath_list *_core_threads = NULL;
static ath_list *_job_threads = NULL;
static ath_list *_engine_threads = NULL;
static apr_pool_t *_corethr_pool = NULL;
static apr_pool_t *_jobthr_pool = NULL;
static apr_pool_t *_enginethr_pool = NULL;
static const ath_list *_startup_propovers = NULL;
static const char *_startup_conffile = NULL;
static pthread_mutex_t *_thrstat_mutex = NULL;
static int _has_agent;
static int _has_monitor;
static int _has_mirror;
static int _has_lb;

static void _core_thread_builder(apr_pool_t *pool);
static void _job_thread_builder(apr_pool_t *pool);
static void _engine_thread_builder(apr_pool_t *pool);
static int _thread_joiner(ath_list *threads);
static void _core_thread_murderer();
static void _job_thread_murderer();
static void _engine_thread_murderer();
static int _sig_handler_shutdown();
static int _sig_handler_stop();
static int _sig_handler_start();
static int _threads_status_lock();
static int _threads_status_unlock();

/* props are 2-tuple, args 1 and 3 key it */
static const athd_conf_prop athd_app_props[] =
  {
      /* instance-wide configs */
      ATHD_CONF_PROP("ServerName", athd_conf_prop_name, NULL),
      ATHD_CONF_PROP("LogLvl", athd_conf_prop_debug, NULL),
      ATHD_CONF_PROP("UseSHM", athd_conf_prop_shm, NULL),
      ATHD_CONF_PROP("UseApacheConf", athd_conf_prop_apconf, NULL),
      ATHD_CONF_PROP("Uid", athd_conf_prop_uid, NULL),
      ATHD_CONF_PROP("Gid", athd_conf_prop_gid, NULL),
      ATHD_CONF_PROP("Port", athd_conf_prop_port, NULL),
      ATHD_CONF_PROP("PidFile", athd_conf_prop_pidfile, NULL),
      ATHD_CONF_PROP("LogFile", athd_conf_prop_logfile, NULL),
      ATHD_CONF_PROP("LogLockPath", athd_conf_prop_loglock, NULL),
      ATHD_CONF_PROP("Shell", athd_conf_prop_shell, NULL),
      ATHD_CONF_PROP("Sendmail", athd_conf_prop_sendmail, NULL),

      /* instance-wide iplb config */
      ATHD_CONF_PROP("Ip", athd_conf_prop_ip, NULL),
      ATHD_CONF_PROP("IpDivertPort", athd_conf_prop_ipport, NULL),
      ATHD_CONF_PROP("IpDenyIncoming", athd_conf_prop_ipdeny, NULL),
      ATHD_CONF_PROP("IpUseSockets", athd_conf_prop_ipsock, NULL),
      /* these props are here because a farm must be unique across engines 
       * in an athena/machine instance
       */
      ATHD_CONF_PROP("IpMapFarmPort", athd_conf_prop_ipmapfarm, NULL),
      ATHD_CONF_PROP("IpFarmsDo", athd_conf_prop_ipfarms, NULL),

      /* isntance-wide stats gathering config */
      ATHD_CONF_PROP("StatsPulse", athd_conf_prop_statspulse, NULL),

      /* per-engine configs */
      ATHD_CONF_PROP("Engine", athd_conf_prop_eng, NULL),
      ATHD_CONF_PROP("KeepAlive", athd_conf_prop_engkeep, "Engine"),
      ATHD_CONF_PROP("RequestQueueLimit", athd_conf_prop_engqlimit, "Engine"),
      ATHD_CONF_PROP("URL", athd_conf_prop_engpri, "Engine"),
      ATHD_CONF_PROP("Secondary", athd_conf_prop_engsec, "Engine"),
      ATHD_CONF_PROP("IpEnable", athd_conf_prop_engip, "Engine"),

      /* per-job configs */
      ATHD_CONF_PROP("Job", athd_conf_prop_job, NULL),
      ATHD_CONF_PROP("Engine", athd_conf_prop_jobeng, "Job"),
      ATHD_CONF_PROP("Type", athd_conf_prop_jobtype, "Job"),
      ATHD_CONF_PROP("Freq", athd_conf_prop_jobfreq, "Job"),
      ATHD_CONF_PROP("Mail", athd_conf_prop_jobmail, "Job"),
      ATHD_CONF_PROP("MailAddr", athd_conf_prop_jobmailaddr, "Job"),
      ATHD_CONF_PROP("MailReplyTo", athd_conf_prop_jobmailreply, "Job"),
      ATHD_CONF_PROP("MailRate", athd_conf_prop_jobmailrate, "Job"),
      {NULL},
  };

static void 
usage()
{
    fprintf(stdout, 
	    "Usage: athenad_cmd [-f confile] [-k signal] [-v] [-X] [-h]\n");
}

int 
main(int argc, const char * const argv[])
{
    const char *str_conffile = ATHD_DEFAULT_CONFFILE;
    apr_pool_t *pglobal;
    apr_pool_t *pchild;
    apr_pool_t *pconf;
    apr_pool_t *popt;
    apr_pool_t *plog;
    apr_getopt_t *opt;
    const char *optarg;
    int rv;
    char c;
    athd_conf *conf;
    char *str_msg;
    ath_list *propovers = NULL;
    apr_proc_t *childproc;
    int exitcode;
    apr_exit_why_e exitwhy;
    int foreground = 0;
    const char *sig_on_startup = NULL;
    pthread_t *watchdog_thread = NULL;
    int *watchdog_sig_exit;

    apr_app_initialize(&argc, &argv, NULL);
    atexit(apr_terminate);
    apr_pool_create(&pglobal, NULL);
    /* signals setup as early as possible */
    /* although aparently this function does nothing */
    apr_signal_init(pglobal);
    _thrstat_mutex = apr_pcalloc(pglobal, sizeof(pthread_t));
    apr_pool_cleanup_register(pglobal, _thrstat_mutex, pthread_mutex_cleanup,
			      apr_pool_cleanup_null);
    pthread_mutex_init(_thrstat_mutex, NULL);
    /* misc pools */
    apr_pool_create(&popt, pglobal);
    apr_pool_create(&plog, pglobal);

    apr_getopt_init(&opt, popt, argc, argv);
    while ((rv = apr_getopt(opt, ATHD_ATHENAD_CMD_ARGS, &c, &optarg))
	   == APR_SUCCESS)
      {
	  char *str_propover;
	  int lvl;
	  switch (c)
	    {
	    case 'v': /* version */
		fprintf(stdout, "%s %s\n", PACKAGE, ATH_PKG_V_LONG);
		athd_proc_exit(0);
		break;
	    case 'f': /* conf file path */
		str_conffile = optarg;
		break;
	    case 'D': /* property (directive) override */
		/* startup overrides survive restarts, use pglobal */
		if (propovers == NULL)
		    propovers = ath_list_ap_alloc(pglobal);
		str_propover = apr_pstrndup(popt, optarg, strlen(optarg));
		ath_list_ap_add(popt, propovers, str_propover);
		break;
	    case 'k': /* send signal */
		sig_on_startup = optarg;
		break;
	    case 'd': /* debug level */
		lvl = atoi(optarg);
		athd_log_set_lvl(lvl);
		break;
	    case 'X': /* run in foreground */
		foreground = 1;
		break;
	    case 'h': /* help */
	    case '?':
		usage();
		athd_proc_exit(0);
	    }      
      }

    /* survives restart signal */
    if (str_conffile)
	_startup_conffile = apr_pstrdup(pglobal, str_conffile);
    if (propovers)
	_startup_propovers = propovers;

    /* conf will create a subpool for itself */
    conf = athd_conf_create(pglobal, str_conffile, athd_app_props);
    athd_conf_current_set(conf);

    /* to fork or not to fork */
    if ((!sig_on_startup) 
	|| (ath_str_to_unix_sig(sig_on_startup) == ATHD_SIGSTRT))
      {
	  /* check if already running */
	  int pidread = athd_proc_read_pidfile();
	  sig_on_startup = NULL;
	  if (pidread > 0)
	    {
		if (athd_proc_pid_is_alive(pidread))
		  {
		      athd_log(ATHD_CORE, ATHD_ERR_PROCRUN, 
			       pidread, conf->pidfile);
		      athd_proc_exit(1);
		  }
		else
		  {
		      athd_log(ATHD_CORE, ATHD_WARN_STALEPID, conf->pidfile);
		      apr_file_remove(conf->pidfile, pglobal);
		  }
	    }
	  if (!foreground)
	    {
		int pid;
		pid = fork();
		if (pid < 0)
		    athd_log(ATHD_CRIT, "Unable to fork to daemon.");
		else if (pid > 0)
		    athd_log(ATHD_CORE, "Forked. [%s]", ATH_PKG_V_LONG);
		if (pid != 0)
		    athd_proc_exit(0);
	    }
      }
  
    if (conf->use_apconf)
      {
	  const char *msg;
	  athd_directive *direc;
	  msg = athd_directive_gen_tree(conf->pool, 
					&conf->ap_conf_tree, conf->use_apconf);
	  if (msg) 
	      athd_log(ATHD_WARN, "Config Warning: %s", msg);
/* 	  if (conf->ap_conf_tree) */
/* 	      direc = athd_directive_for_cmd(conf->ap_conf_tree, "AthSHMFile"); */
      }
    /* process -D overrides */
    if (propovers)
	conf = athd_conf_overrides(conf, athd_app_props, propovers);


    /* app setup begin */
    /* all others depend on watchdog setup to register cleanup callbacks */
    watchdog_thread = athd_watchdog_thread_setup(pglobal);    
    athd_log_init(pglobal);
    /* run levels depend on watchdog thread setup */
    athd_sig_handler_register(_sig_handler_shutdown, 
			      "Athena (main)", ATHD_SHUTDOWN);
    athd_sig_handler_register(_sig_handler_stop, 
			      "Athena (main)", ATHD_STOP);
    athd_sig_handler_register(_sig_handler_start, 
			      "Athena (main)", ATHD_START);
    /* am i only starting to signal daemon? */
    if (sig_on_startup)
      {
	  athd_proc_sig_str(sig_on_startup);
	  athd_proc_exit(0);
      }
    /* now really begin */
    athd_log(ATHD_CORE, "Configure and init: [%s] pgid: [%d] log level: %s", 
	     ATH_PKG_V, GET_PID(), athd_log_get_strlvl());
    conf->passchecks = 1;
    /* record globals */
    _current_pglobal = pglobal;   
    /* pre-ready state setup */
    athd_proc_write_pidfile();
    athd_connection_shared_setup(pglobal);
    athd_mail_thread_setup(pglobal);
    athd_engine_thread_shared_setup(pglobal);
    athd_job_thread_shared_setup(pglobal);
    athd_lb_thread_setup(pglobal);
    athd_stats_thread_setup(pglobal);
    athd_cmd_handler_shared_setup(pglobal);
/*     athd_listen_thread_setup(pglobal); */
    /* app setup end */

    /* post setup clean up stage */
    apr_pool_destroy(popt);

    /* build and launch top lvl threads */
    _core_thread_builder(pglobal);
    _engine_thread_builder(pglobal);
    _job_thread_builder(pglobal);

    /* our super duper looper */
    pthread_join(*(watchdog_thread), (void *)&watchdog_sig_exit);
    athd_proc_shutdown(*(watchdog_sig_exit));
    /* goodbye */
}

/* this stage is single threaded */
static void
_core_thread_builder(apr_pool_t *pglobal)
{
    int rv = APR_SUCCESS;
    pthread_t *thread;
    apr_pool_t *corethr_pool;
    ath_list *threads;
    athd_conf *conf = athd_conf_current_get();
    ASSERT(conf);
    /* this should never be re-inited */
    ASSERT(!_corethr_pool);
    _threads_status_lock();

    apr_pool_create(&corethr_pool, pglobal);
    threads = ath_list_ap_alloc(corethr_pool);

    thread = apr_pcalloc(corethr_pool, sizeof(pthread_t));
    if (pthread_create(thread, NULL, athd_mail_thread, conf))
	ASSERT(0);
    else
	ath_list_ap_add(corethr_pool, threads, thread);

    thread = apr_pcalloc(corethr_pool, sizeof(pthread_t));
    if (pthread_create(thread, NULL, athd_lb_thread, conf))
	ASSERT(0);
    else
	ath_list_ap_add(corethr_pool, threads, thread);

    thread = apr_pcalloc(corethr_pool, sizeof(pthread_t));
    if (pthread_create(thread, NULL, athd_stats_thread, conf))
	ASSERT(0);
    else
	ath_list_ap_add(corethr_pool, threads, thread);

/*     thread = apr_pcalloc(corethr_pool, sizeof(pthread_t)); */
/*     if (pthread_create(thread, NULL, athd_listen_thread, conf)) */
/* 	ASSERT(0); */
/*     else */
/* 	ath_list_ap_add(corethr_pool, threads, thread); */

    _corethr_pool = corethr_pool;
    _core_threads = threads;
    _threads_status_unlock();
}

static void
_job_thread_builder(apr_pool_t *pglobal)
{
    int rv = APR_SUCCESS;
    ath_node *listctx;
    apr_pool_t *jthr_pool;
    ath_list *threads;
    athd_conf *conf = athd_conf_current_get();
    ath_list *jobs;
    ASSERT(conf);
    jobs = conf->jobs;
    _threads_status_lock();
    if (!_jobthr_pool)
	apr_pool_create(&_jobthr_pool, pglobal);
    threads = ath_list_ap_alloc(_jobthr_pool);
    ath_list_rewind_r(jobs, &listctx);
    while ((!rv) && (ath_list_hasnext_r(jobs, &listctx)))
      {
	  athd_job *job = ath_list_getnext_r(jobs, &listctx);
	  pthread_t *thread;
	  //if (*(job->thread)) /* guarantee only one thread/job */
	  //  pthread_join(*(job->thread), NULL);
	  /* the above is done in murderer now */
	  if (!job->engine) 
	    {
		athd_log(ATHD_WARN, "No engine for job: %s", job->name);
		athd_job_destroy(&job);
		ath_list_remove_prev_r(jobs, &listctx);
	    }
	  else
	    {
		job->dienow = 0;
		thread = apr_pcalloc(_jobthr_pool, sizeof(pthread_t));
		if (job->type == ATHD_JOB_MON)
		    rv = pthread_create(thread, NULL, 
					athd_job_thread_monitor, job);
		else if (job->type == ATHD_JOB_MIR)
		    rv = pthread_create(thread, NULL, 
					athd_job_thread_mirror, job);
		else if (job->type == ATHD_JOB_AGT)
		    rv = pthread_create(thread, NULL, 
					athd_job_thread_agent, job);
		else
		  {
		      athd_log(ATHD_CRIT, "No matching job type for: %s", job->name);
		      athd_job_destroy(&job);
		      ath_list_remove_prev_r(jobs, &listctx);
		      thread = NULL;
		  }
		if ((!rv) && (thread))
		  {
		      job->thread = thread;
		      ath_list_ap_add_tail(_jobthr_pool, threads, thread);
		  }
		else
		    athd_log_err(ATHD_CORE, rv, "Could not create job thread: ");
	    }
      }
    _threads_status_unlock();
}

static void
_engine_thread_builder(apr_pool_t *pglobal)
{
    int rv = APR_SUCCESS;
    ath_node *listctx;
    apr_pool_t *ethr_pool;
    ath_list *threads;
    athd_conf *conf = athd_conf_current_get();
    ath_list *engines;
    ASSERT(conf);
    engines = conf->engines;
    _threads_status_lock();
    if (!_enginethr_pool)
	apr_pool_create(&_enginethr_pool, pglobal);
    threads = ath_list_ap_alloc(_enginethr_pool);
    ath_list_rewind_r(engines, &listctx);
    while ((!rv) && (ath_list_hasnext_r(engines, &listctx)))
      {
	  athd_engine *engine = ath_list_getnext_r(engines, &listctx);
	  pthread_t *thread;
	  //if (*(engine->thread)) /* guarantee only one thread/job */
	  //   pthread_join(*(engine->thread), NULL);
	  engine->dienow = 0;
	  thread = apr_pcalloc(_enginethr_pool, sizeof(pthread_t));
	  rv = pthread_create(thread, NULL, athd_engine_thread, engine);
	  if ((!rv) && (thread))
	    {
		engine->thread = thread;
		ath_list_ap_add_tail(_enginethr_pool, threads, thread);
	    }
	  else
	      athd_log_err(ATHD_CORE, rv, "Could not create job thread: ");
	  /* this stage is single threaded */
	  /* pthread_mutex_unlock(job->mutex); */
      }
    _threads_status_unlock();
}

static void
_core_thread_murderer()
{
    ath_node *listctx;
    _threads_status_lock();
    ath_list_rewind_r(_core_threads, &listctx);
    while (ath_list_hasnext_r(_core_threads, &listctx))
      {
	  pthread_t *thread = (pthread_t *)
	    ath_list_popnext_r(_core_threads, &listctx);
	  pthread_cancel(*(thread));
	  pthread_join(*(thread), NULL);
      }
    apr_pool_clear(_corethr_pool);
    _threads_status_unlock();
}

static void
_job_thread_murderer()
{
    ath_node *listctx;
    athd_conf *conf = athd_conf_current_get();
    ath_list *jobs;
    ASSERT(conf);
    jobs = conf->jobs;
    _threads_status_lock();
    ath_list_rewind_r(jobs, &listctx);
    while (ath_list_hasnext_r(jobs, &listctx))
      {
	  athd_job *job = ath_list_getnext_r(jobs, &listctx);
	  ASSERT(job);
	  pthread_cancel(*(job->thread));
	  pthread_join(*(job->thread), NULL);
      }
    _threads_status_unlock();
}

static void
_engine_thread_murderer()
{
    ath_node *listctx;
    athd_conf *conf = athd_conf_current_get();
    ath_list *engines;
    ASSERT(conf);
    engines = conf->engines;
    _threads_status_lock();
    ath_list_rewind_r(engines, &listctx);
    while (ath_list_hasnext_r(engines, &listctx))
      {
	  athd_engine *engine = ath_list_getnext_r(engines, &listctx);
	  pthread_cancel(*(engine->thread));
	  pthread_join(*(engine->thread), NULL);
      }
    _threads_status_unlock();
}

/* this cleans up alot of shit, frees memory
   destroys all restartable threads
*/
static int
_sig_handler_stop()
{
    athd_conf *newconf;
    ASSERT(_thrstat_mutex);
    ASSERT(_current_pglobal);
    _job_thread_murderer();
    _engine_thread_murderer();
    _threads_status_lock();
    apr_pool_clear(_jobthr_pool);
    apr_pool_clear(_enginethr_pool);
    _threads_status_unlock();
    return APR_SUCCESS;
}

/* rebuild rebuildable threads */
static int
_sig_handler_start()
{
    athd_conf *newconf;
    ASSERT(_current_pglobal);
    _job_thread_builder(_current_pglobal);
    _engine_thread_builder(_current_pglobal);
    return APR_SUCCESS;
}

static int
_sig_handler_shutdown()
{
    _core_thread_murderer();
    return APR_SUCCESS;
}

static int
_threads_status_lock()
{
    return pthread_mutex_lock(_thrstat_mutex);
}

static int
_threads_status_unlock()
{
    return pthread_mutex_unlock(_thrstat_mutex);
}
